/*
 * main.cpp
 *
 *  Created on: Aug 26, 2011
 *      Author: Jon
 */

//	DDRB = _BV(PB0);//Pinmode(8, OUTPUT);
//	PORTB = _BV(PB0); //digitalWrite(8, HIGH);


#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include "lcd.h"

#define delay(x) _delay_ms(x)
#define delay_us(x) _delay_us(x)
void delay_us(unsigned long delay)
{
	while(delay--) asm volatile("nop");
}

#define LEFT_PIN PC1
#define RIGHT_PIN PC2
#define RELEASE_PIN PC0

#define ALL_BUTTON_MASK 0x1F
#define RELEASE_MASK 0x01
#define LEFT_MASK 0x02
#define RIGHT_MASK 0x04

#define MOTOR_STEPS 283


typedef enum TIMES
{
	THREE = 3/*3*3600*/,
	FIVE = 17999/*5*3600*/,
	SEVEN = 25198/*7*3600*/,
	NINE = 32398/*9*3600*/,
	TWELVE = 43198/*12*3600*/
}TIMES;

void setupTimer()
{
  TCCR1B = _BV(CS12)| _BV(CS10) | _BV(WGM12);//--CLK/1024 = 7812.5Hz
  /*Compare Match every 1 second*/
  uint16_t counts = 7813;//attiny88 = 1.000064 (1sec)
  OCR1B = counts;
  TIMSK1 = _BV(OCIE1B);
}


void step(uint16_t steps, uint8_t direction, volatile uint8_t * motor_pos)
{
  uint8_t motor_step[4] = {0x1a, 0x16, 0x15, 0x19};//full step
  //uint8_t motor_step[8] = {0x18, 0x1a, 0x12, 0x16, 0x14, 0x15, 0x11, 0x19};//half step
  uint8_t motor_position = *motor_pos;
  for(uint16_t i=0;i<steps;i++)
  {
    PORTD = motor_step[motor_position];
    delay(10);  // delay time for each step
    if(direction)
    {
      motor_position++;
    }
    else
    {
      motor_position--;
    }
    motor_position = motor_position & 0x03;//full step
    //motor_position = motor_position & 0x07;//half step
  }
  *motor_pos = motor_position;
  PORTD = 0x00;
}

volatile uint32_t seconds = 0; //Max time is: ~18 hours
volatile uint32_t time_delay = TWELVE;
volatile uint8_t motor_position = 0;

int main(void)
{

	//Buttons
	DDRC &= ~_BV(PC0) | ~_BV(PC1) | ~_BV(PC2) | ~_BV(PC3) | ~_BV(PC4) | ~_BV(PC5);//inputs
	PORTC = _BV(PC0) | _BV(PC1) | _BV(PC2) | _BV(PC3) | _BV(PC4) | _BV(PC5);//Pull ups

	//Motor Pins
	DDRD = 0x1f;//pinMode(0-7, OUTPUT);
	DDRB = _BV(LCD_DATA0_PIN) | _BV(LCD_DATA1_PIN) | _BV(LCD_DATA2_PIN) | _BV(LCD_DATA3_PIN);

	lcd_init();

	_delay_ms(500);

	cli();
	setupTimer();
	_delay_ms(500);
	sei();

	lcd_string("   Kitteh....   ");
	_delay_ms(1000);

	uint8_t loop_count=0;
	uint32_t seconds_left;
	uint8_t input;
	char buffer[16];
	while(1)
	{
		input= PINC;
		if(false)
		{
			time_delay+=3000;//30 Minutes
			seconds_left = (time_delay-seconds);
			uint8_t hours = (uint8_t)seconds_left/3600;
			uint8_t minutes = (uint8_t) (seconds_left/60 - hours*60);
			uint8_t seconds_rem = seconds_left - hours*3600 - minutes*60;
			sprintf(buffer, "%2u:%2u:%2u",hours, minutes, seconds_rem);
			lcd_gotoxy(0,0);
			lcd_string("    Time left   ");
			lcd_setline(1);
			lcd_string(buffer);
		}
		if(false)
		{
			time_delay-=3000;//30 Minutes
			seconds_left = (time_delay-seconds);
			uint8_t hours = (uint8_t)seconds_left/3600;
			uint8_t minutes = (uint8_t) (seconds_left/60 - hours*60);
			uint8_t seconds_rem = seconds_left - hours*3600 - minutes*60;
			sprintf(buffer, "%2u:%2u:%2u",hours, minutes, seconds_rem);
			lcd_gotoxy(0,0);
			lcd_string("    Time left   ");
			lcd_setline(1);
			lcd_string(buffer);
		}
		if(bit_is_clear(input, RELEASE_PIN) || bit_is_clear(input, LEFT_PIN) || bit_is_clear(input, RIGHT_PIN))
		{
			lcd_gotoxy(0,0);
			lcd_string(" Feeding Kitteh ");
			step(283, 1, &motor_position);
			seconds = 0;
		}
		if(!(loop_count++ & 255))
		{
			seconds_left = (time_delay-seconds);
			uint8_t hours = (uint8_t)seconds_left/3600;
			uint8_t minutes = (uint8_t) (seconds_left/60 - hours*60);
			uint8_t seconds_rem = seconds_left - hours*3600 - minutes*60;
			sprintf(buffer, "%2u:%2u:%2u",hours, minutes, seconds_rem);
			lcd_gotoxy(0,0);
			lcd_string("    Time left   ");
			lcd_setline(1);
			lcd_string(buffer);
		}
    }
	return 0;
}


/*
 * Timer Interrupt Routine
 */
ISR(TIMER1_COMPB_vect)
{
	seconds++;
	if(seconds>=time_delay)
	{
		//step(67, 1, &motor_position);//full step
		lcd_gotoxy(0,0);
		lcd_string(" Feeding Kitteh ");
		step(283, 1, &motor_position);//half step
		seconds = 0;
	}
	TCNT1=0;
}


/**
 * LCD Functions
 */

//-----------------------------------------------------------------------------------------
// FUNCTION: _auxToggleE
// PURPOSE: flush channel E
void _auxToggleE(void)
{
    lcd_e_high();
    //lcd_e_delay();
	delay_us(us(600));	//sensitive when changing CPU MHz!!!!!!!!!!!!!
    lcd_e_low();
}

//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_write
// PURPOSE: send a character or an instruction to the LCD
void lcd_write(uint8_t data,uint8_t rs)
{
	//check write type
    if (rs)
       lcd_rs_high(); //write data
    else
       lcd_rs_low();  //write instruciton

    // configure data pins as output
    LCD_DDR(LCD_DATA0_PORT) |= _BV(LCD_DATA0_PIN);
    LCD_DDR(LCD_DATA1_PORT) |= _BV(LCD_DATA1_PIN);
    LCD_DDR(LCD_DATA2_PORT) |= _BV(LCD_DATA2_PIN);
    LCD_DDR(LCD_DATA3_PORT) |= _BV(LCD_DATA3_PIN);

    // output high nibble first
    LCD_DATA3_PORT &= ~_BV(LCD_DATA3_PIN);
    LCD_DATA2_PORT &= ~_BV(LCD_DATA2_PIN);
    LCD_DATA1_PORT &= ~_BV(LCD_DATA1_PIN);
    LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN);
	if(data & 0x80) LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN);
	if(data & 0x40) LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN);
	if(data & 0x20) LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN);
	if(data & 0x10) LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN);
    lcd_e_toggle();

    // output low nibble
    LCD_DATA3_PORT &= ~_BV(LCD_DATA3_PIN);
    LCD_DATA2_PORT &= ~_BV(LCD_DATA2_PIN);
    LCD_DATA1_PORT &= ~_BV(LCD_DATA1_PIN);
    LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN);
	if(data & 0x08) LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN);
	if(data & 0x04) LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN);
	if(data & 0x02) LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN);
	if(data & 0x01) LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN);
    lcd_e_toggle();

    // all data pins high (inactive)
    LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN);
    LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN);
    LCD_DATA2_PORT |= _BV(LCD_DATA2_PIN);
    LCD_DATA3_PORT |= _BV(LCD_DATA3_PIN);
}

//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_char
// PURPOSE:  send a character to the LCD
void lcd_char(uint8_t data)
{
	if (data=='\n')
	{
		if (g_nCurrentLine >= LCD_LINES - 1)
			lcd_setline(0);
		else
			lcd_setline(g_nCurrentLine+1);
	}
	else
	lcd_write(data,1);
}

//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_instr
// PURPOSE:  send an instruction to the LCD
void lcd_instr(uint8_t instr)
{
	lcd_write(instr,0);
}

//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_init
// PURPOSE:  Initialize LCD to 4 bit I/O mode
void lcd_init()
{
    // configure all port bits as output (LCD data and control lines on different ports
    LCD_DDR(LCD_RS_PORT)    |= _BV(LCD_RS_PIN);
    LCD_DDR(LCD_E_PORT)     |= _BV(LCD_E_PIN);
    LCD_DDR(LCD_DATA0_PORT) |= _BV(LCD_DATA0_PIN);
    LCD_DDR(LCD_DATA1_PORT) |= _BV(LCD_DATA1_PIN);
    LCD_DDR(LCD_DATA2_PORT) |= _BV(LCD_DATA2_PIN);
    LCD_DDR(LCD_DATA3_PORT) |= _BV(LCD_DATA3_PIN);
    // wait 16ms or more after power-on
    delay_us(us(16000));
    // initial write to lcd is 8bit
    LCD_DATA1_PORT |= _BV(LCD_DATA1_PIN);  // _BV(LCD_FUNCTION)>>4;
    LCD_DATA0_PORT |= _BV(LCD_DATA0_PIN);  // _BV(LCD_FUNCTION_8BIT)>>4;
    lcd_e_toggle();
    delay_us(us(4992)); //4992        // delay, busy flag can't be checked here
    // repeat last command
    lcd_e_toggle();
    delay_us(us(64)); //64          // delay, busy flag can't be checked here
    // now configure for 4bit mode
    LCD_DATA0_PORT &= ~_BV(LCD_DATA0_PIN);   // LCD_FUNCTION_4BIT_1LINE>>4
    lcd_e_toggle();
    delay_us(us(2000));           // some displays need this additional delay
    //
	// from now the LCD only accepts 4 bit I/O
	// start config
	lcd_clrscr();


    lcd_instr(LCD_FUNCTION_4BIT_2LINES); // 4-bit interface, dual line,   5x7 dots
    lcd_e_toggle();
	delay_us(us(2000));           // some displays need this additional delay


    lcd_instr(LCD_ENTRY_INC_);//cursor move right, no shift display
	lcd_e_toggle();
	delay_us(us(2000));           // some displays need this additional delay


    lcd_instr(LCD_DISP_ON_CURSOR_BLINK);// display on, cursor on, blink char
	lcd_e_toggle();
	delay_us(us(2000));           // some displays need this additional delay


    lcd_home;//set cursor to home and clear the cursor

}

//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_newline
// PURPOSE:  Move cursor on specified line
void lcd_setline(uint8_t line)
{
    uint8_t addressCounter = 0;
	switch(line)
	{
		case 0: addressCounter = LCD_START_LINE1; break;
		case 1: addressCounter = LCD_START_LINE2; break;
		case 2: addressCounter = LCD_START_LINE3; break;
		case 3: addressCounter = LCD_START_LINE4; break;
		default:addressCounter = LCD_START_LINE1; break;
	}
	g_nCurrentLine = line;

    lcd_instr((1<<LCD_DDRAM)+addressCounter);
}

int g_nCurrentLine = 0;

//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_string
// PURPOSE:  send a null terminated string to the LCD eg. char x[10]="hello!";
void lcd_string(char *text)
{
	char c;
    while ( (c = *text++) )  lcd_char(c);
}

void lcd_string2(char *szFormat, ...)
{
    char szBuffer[256]; //in this buffer we form the message
    int NUMCHARS = sizeof(szBuffer) / sizeof(szBuffer[0]);
    int LASTCHAR = NUMCHARS - 1;
    va_list pArgs;
    va_start(pArgs, szFormat);
    vsnprintf(szBuffer, NUMCHARS - 1, szFormat, pArgs);
    va_end(pArgs);

	lcd_string(szBuffer);

}
//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_gotoxy
// PURPOSE:  Set cursor to specified position
//           Input:    x  horizontal position  (0: left most position)
//                     y  vertical position    (0: first line)
void lcd_gotoxy(uint8_t x, uint8_t y)
{
#if LCD_LINES==1
    lcd_instr((1<<LCD_DDRAM)+LCD_START_LINE1+x);
#elif LCD_LINES==2
   	switch (y)
	{
		case 0:lcd_instr((1<<LCD_DDRAM)+LCD_START_LINE1+x);break;
    	case 1:lcd_instr((1<<LCD_DDRAM)+LCD_START_LINE2+x);break;
		default: break;
	}
#elif LCD_LINES==4
   	switch (y)
	{
		case 0:lcd_instr((1<<LCD_DDRAM)+LCD_START_LINE1+x);break;
    	case 1:lcd_instr((1<<LCD_DDRAM)+LCD_START_LINE2+x);break;
    	case 2:lcd_instr((1<<LCD_DDRAM)+LCD_START_LINE3+x);break;
    	case 3:lcd_instr((1<<LCD_DDRAM)+LCD_START_LINE4+x);break;
		default: break;
	}
#endif
}


//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_clrscr
// PURPOSE:  Clear display and set cursor to home position
void lcd_clrscr(void)
{
	g_nCurrentLine = 0;
   	lcd_instr(1<<LCD_CLR);
	delay_us(us(500));
}

//-----------------------------------------------------------------------------------------
// FUNCTION: lcd_home
// PURPOSE:  Set cursor to home position
void lcd_home(void)
{
	g_nCurrentLine = 0;
    lcd_instr(1<<LCD_HOME);
//	delay_us(us(2000));
}
